//////////////////////////////////////////////////////////////////////////

#ifndef _CLIENTCONNECTION_H_
#include "clientconnection.h"
#endif

//////////////////////////////////////////////////////////////////////////

#ifndef _BOOSTSERVICE_H_
#include "boostservice.h"
#endif

#include <iostream>
#include <boost/lexical_cast.hpp>

//////////////////////////////////////////////////////////////////////////


ClientConnection::ClientConnection()
: Connection( TCPSocketPtr(new boost::asio::ip::tcp::socket( BoostService::GetIOService() ) ) ),
  m_UDPSocket( BoostService::GetIOService() ),
  m_ID(-1)
{
}


//////////////////////////////////////////////////////////////////////////


ClientConnection::~ClientConnection()
{
}


//////////////////////////////////////////////////////////////////////////


void ClientConnection::Connect( const char* _address, int _port )
{
    m_PortStr = boost::lexical_cast<std::string>(_port);

    EstablishTCP(_address, _port);
    StartReceiveTCP();
    EstablishUDP(_address, _port);
    StartReceiveUDP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnection::EstablishTCP( const char* _address, int _port )
{
    
    // Resolve address
    boost::asio::ip::tcp::resolver resolver( BoostService::GetIOService() );
    boost::asio::ip::tcp::resolver::query query( _address,  m_PortStr.c_str() );
    boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    boost::asio::ip::tcp::resolver::iterator end;

    boost::system::error_code error = boost::asio::error::host_not_found;
    while (error && endpoint_iterator != end)
    {
        m_TCPSocket->close();

        // Connect to server
        m_TCPSocket->connect(*endpoint_iterator, error);

        ++endpoint_iterator;
    }

    // Check for errors
    if (error)
    {
        std::cout << "ERROR CONNECT TCP: " << error.message() << std::endl;
        return;
    }

    boost::asio::ip::tcp::no_delay option(true);
    m_TCPSocket->set_option(option);
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnection::EstablishUDP( const char* _address, int _port )
{
    // Resolve server address
    boost::asio::ip::udp::resolver resolver( BoostService::GetIOService() );
    boost::asio::ip::udp::resolver::query query( boost::asio::ip::udp::v4(), _address, m_PortStr );
    m_UDPEndpoint = *resolver.resolve(query);

    boost::system::error_code error;

    // Open UDP socket
    m_UDPSocket.open( boost::asio::ip::udp::v4(), error );

    if (error)
    {
        std::cout << "Error opening UDP socket: " << error.message() << std::endl;
        return;
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnection::SendUDP( const NetworkPacket& _data )
{
    if ( m_ID != -1 )
    {
        char ID = (char)m_ID;
        boost::array<boost::asio::const_buffer, 2> buffers = {
            boost::asio::buffer( &ID, 1 ),
            boost::asio::buffer( _data.Data(), _data.Size() )
        };

        // Send data through UDP
        m_UDPSocket.send_to(buffers, m_UDPEndpoint);
    }
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnection::StartReceiveUDP()
{
    // Recv data through UDP
    m_UDPSocket.async_receive( boost::asio::buffer(m_RecvBufferUDP, MAX_BUFFER_SIZE),
        boost::bind( &ClientConnection::HandleRecvUDP,
                     this,
                     boost::asio::placeholders::error,
                     boost::asio::placeholders::bytes_transferred ) );
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnection::HandleRecvUDP( const boost::system::error_code& _error, size_t _bytesTransferred )
{
    // Check for errors
    if (_error)
    {
        // Some strange bugs...
        switch (_error.value())
        {
            case 10054:     // An existing connection was forcibly closed by the remote host.
                throw "Connection to server has been dropped.";
                break;

            case 10061:     // No connection could be made because the target machine actively refused it.
            case 995:       // The I/O operation has been aborted because of either a thread exit or an application request.
            case 234:       // More data is available.
                std::cout <<"ERROR RECV UDP #" <<_error.value() <<": "<< _error.message() << std::endl;

            case 10022:     // An invalid argument was supplied.
                return StartReceiveUDP();
                break;

            default:
                throw _error.message().c_str();
        }
    }

    m_UDPRecvPacket = NetworkPacket(m_RecvBufferUDP, _bytesTransferred);
    NotifyObservers(Observer::NETWORK_RECEIVE_COMPLETE_UDP);

    // Start receive UDP again
    StartReceiveUDP();
}


////////////////////////////////////////////////////////////////////////////////


void ClientConnection::SetupUDP( int _clientID )
{
    m_ID = _clientID;
}


////////////////////////////////////////////////////////////////////////////////
